#include "StdAfx.h"
#include "GameObject.h"
#include "Timer.h"
#include "Camera.h"
#include "Player.h"
#include "Scene.h"

CPlayer::CPlayer()
{
    m_pCamera = NULL;
    m_p3rdPersonObject = NULL;
    m_nCameraMode = 0;

    m_nUpdatePlayerCount = 0;
    m_nUpdateCameraCount = 0;
	m_pUpdatePlayer = NULL;
	m_pUpdateCamera = NULL;

    m_vPosition = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
    m_vRight = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
    m_vUp = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
    m_vLookAt = D3DXVECTOR3(0.0f, 0.0f, 1.0f);

	m_bBaseTransform = false;
	D3DXMatrixIdentity(&m_mMeshBaseTransform);
	m_fFloat = 0.0f;

    m_vCameraOffset = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
    m_fCameraLag = 0.0f;

    m_vVelocity = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
    m_vGravity = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
    m_fMaxVelocityXZ = 125.0f;
    m_fMaxVelocityY = 125.0f;
    m_fFriction = 250.0f;

	m_fPitch = 0.0f;             
    m_fRoll = 0.0f;              
    m_fYaw = 0.0f;               

    m_bcBoundingCube.m_vMinimum = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
    m_bcBoundingCube.m_vMaximum = D3DXVECTOR3(0.0f, 0.0f, 0.0f);    
}

CPlayer::~CPlayer()
{
    if (m_pCamera) delete m_pCamera;
    m_pCamera = NULL;
    if (m_p3rdPersonObject) delete m_p3rdPersonObject;
    m_p3rdPersonObject = NULL;
}

void CPlayer::SetCameraOffset(const D3DXVECTOR3& vOffset)
{
    m_vCameraOffset = vOffset;
    if (m_pCamera) m_pCamera->SetPosition(m_vPosition + vOffset);
}

void CPlayer::UpdateVelocity(DWORD dwDirection, float fDistance)
{
    D3DXVECTOR3 vShift = D3DXVECTOR3(0, 0, 0);
    if (dwDirection & DIR_FORWARD) vShift += m_vLookAt * fDistance;
    if (dwDirection & DIR_BACKWARD) vShift -= m_vLookAt * fDistance;
    if (dwDirection & DIR_RIGHT) vShift += m_vRight * fDistance;
    if (dwDirection & DIR_LEFT) vShift -= m_vRight * fDistance;
    if (dwDirection & DIR_UP) vShift += m_vUp * fDistance;
    if (dwDirection & DIR_DOWN) vShift -= m_vUp * fDistance;

	if (dwDirection) m_vVelocity += vShift;
}

bool CPlayer::Move(const D3DXVECTOR3& vShift)
{
	D3DXVECTOR3 vPreviousPosition = m_vPosition;
	m_vPosition = m_vPosition + vShift;
	bool bCanBeUpdated = true;

    for (int i = 0; i < m_nUpdatePlayerCount; i++)
    {
        LPUPDATEPLAYERCALLBACK pfUpdatePlayer = (LPUPDATEPLAYERCALLBACK)m_pUpdatePlayer[i].m_pfCallback;
        if (!(bCanBeUpdated = pfUpdatePlayer(m_pUpdatePlayer[i].m_pContext, this, 0.0f))) break;
    } 

	if (bCanBeUpdated) 
		m_pCamera->Move(vShift);    
	else
		m_vPosition = vPreviousPosition;

	return(bCanBeUpdated);
}

void CPlayer::Rotate(float x, float y, float z)
{
    D3DXMATRIX mtxRotate;
    if (!m_pCamera) return;
    if ((m_nCameraMode == FIRST_PERSON_CAMERA) || (m_nCameraMode == THIRD_PERSON_CAMERA))
    {
        if (x != 0.0f)
        {
            m_fPitch += x;
            if (m_fPitch > +89.0f) { x -= (m_fPitch - 89.0f); m_fPitch = +89.0f; }
            if (m_fPitch < -89.0f) { x -= (m_fPitch + 89.0f); m_fPitch = -89.0f; }
        } 
        if (y != 0.0f)
        {
            m_fYaw += y;
            if (m_fYaw > 360.0f) m_fYaw -= 360.0f;
            if (m_fYaw < 0.0f) m_fYaw += 360.0f;
        } 
        if (z != 0.0f) 
        {
            m_fRoll += z;
            if (m_fRoll > +20.0f) { z -= (m_fRoll - 20.0f); m_fRoll = +20.0f; }
            if (m_fRoll < -20.0f) { z -= (m_fRoll + 20.0f); m_fRoll = -20.0f; }
        } 
        m_pCamera->Rotate(x, y, z);
        if (y != 0.0f)
        {
            D3DXMatrixRotationAxis(&mtxRotate, &m_vUp, D3DXToRadian(y));
            D3DXVec3TransformNormal(&m_vLookAt, &m_vLookAt, &mtxRotate);
            D3DXVec3TransformNormal(&m_vRight, &m_vRight, &mtxRotate);
        } 
    } 
    else if (m_nCameraMode == SPACESHIP_CAMERA)
    {
        m_pCamera->Rotate(x, y, z);
        if (x != 0) 
        {
            D3DXMatrixRotationAxis(&mtxRotate, &m_vRight, D3DXToRadian(x));
            D3DXVec3TransformNormal(&m_vLookAt, &m_vLookAt, &mtxRotate);
            D3DXVec3TransformNormal(&m_vUp, &m_vUp, &mtxRotate);
        } 
        if (y != 0) 
        {
            D3DXMatrixRotationAxis(&mtxRotate, &m_vUp, D3DXToRadian(y));
            D3DXVec3TransformNormal(&m_vLookAt, &m_vLookAt, &mtxRotate);
            D3DXVec3TransformNormal(&m_vRight, &m_vRight, &mtxRotate);
        } 
        if (z != 0) 
        {
            D3DXMatrixRotationAxis(&mtxRotate, &m_vLookAt, D3DXToRadian(z));
            D3DXVec3TransformNormal(&m_vUp, &m_vUp, &mtxRotate);
            D3DXVec3TransformNormal(&m_vRight, &m_vRight, &mtxRotate);        
        }    
    } 
    D3DXVec3Normalize(&m_vLookAt, &m_vLookAt);
    D3DXVec3Cross(&m_vRight, &m_vUp, &m_vLookAt);
    D3DXVec3Normalize(&m_vRight, &m_vRight);
    D3DXVec3Cross(&m_vUp, &m_vLookAt, &m_vRight);
    D3DXVec3Normalize(&m_vUp, &m_vUp);
}

void CPlayer::Update(float fTimeScale)
{
    m_vVelocity += m_vGravity * fTimeScale;
    float fLength = sqrtf(m_vVelocity.x * m_vVelocity.x + m_vVelocity.z * m_vVelocity.z);
    if (fLength > m_fMaxVelocityXZ)
    {
        m_vVelocity.x *= (m_fMaxVelocityXZ / fLength);
        m_vVelocity.z *= (m_fMaxVelocityXZ / fLength);    
    } 
    fLength = sqrtf(m_vVelocity.y * m_vVelocity.y);
    if (fLength > m_fMaxVelocityY) m_vVelocity.y *= (m_fMaxVelocityY / fLength);

	bool bUpdated = Move(m_vVelocity * fTimeScale);
	if (bUpdated)
	{
		m_pCamera->Update(fTimeScale, m_fCameraLag);
		for (int j = 0; j < m_nUpdateCameraCount; j++)
		{
			LPUPDATECAMERACALLBACK pfUpdateCamera = (LPUPDATECAMERACALLBACK)m_pUpdateCamera[j].m_pfCallback;
			pfUpdateCamera(m_pUpdateCamera[j].m_pContext, m_pCamera, 0.0f);
		} 
		if (m_nCameraMode == THIRD_PERSON_CAMERA) ((CThirdPersonCamera *)m_pCamera)->SetLookAt(m_vPosition);

		D3DXVECTOR3 vDeceleration = -m_vVelocity;
		D3DXVec3Normalize(&vDeceleration, &vDeceleration);
		fLength = D3DXVec3Length(&m_vVelocity);
		float fDeceleration = (m_fFriction * fTimeScale);
		if (fDeceleration > fLength) fDeceleration = fLength;
		m_vVelocity += vDeceleration * fDeceleration;
	}	
}

bool CPlayer::SetCameraMode(ULONG nCameraMode)
{
    if (m_pCamera && (m_nCameraMode == nCameraMode)) return(true);
    CCamera *pNewCamera = NULL;
    switch (nCameraMode)
    {
        case FIRST_PERSON_CAMERA:            
            if (!(pNewCamera = new CFirstPersonCamera(m_pCamera))) return(false);
            break;
        case THIRD_PERSON_CAMERA:
            if (!(pNewCamera = new CThirdPersonCamera(m_pCamera))) return(false);
            break;
        case SPACESHIP_CAMERA:            
            if (!(pNewCamera = new CSpaceShipCamera(m_pCamera))) return(false);
            break;  
		default:
			return(false);
			break;
    } 
    if (m_pCamera && (m_nCameraMode == SPACESHIP_CAMERA))
    {
        m_vUp = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
        m_vRight.y = 0.0f;
        m_vLookAt.y = 0.0f;
        D3DXVec3Normalize(&m_vRight, &m_vRight);
        D3DXVec3Normalize(&m_vLookAt, &m_vLookAt);
        m_fPitch = 0.0f;
        m_fRoll = 0.0f;
        m_fYaw = D3DXToDegree(acosf(D3DXVec3Dot(&D3DXVECTOR3(0.0f, 0.0f, 1.0f), &m_vLookAt)));
        if (m_vLookAt.x < 0.0f) m_fYaw = -m_fYaw;
    } 
    else if (m_pCamera && (nCameraMode == SPACESHIP_CAMERA))
    {
        m_vRight = m_pCamera->GetRightVector();
        m_vLookAt = m_pCamera->GetLookAtVector();
        m_vUp = m_pCamera->GetUpVector();
    } 

    m_nCameraMode = nCameraMode;
    if (pNewCamera) pNewCamera->AttachToPlayer(this);
    if (m_pCamera) delete m_pCamera;
    m_pCamera = pNewCamera;

	return(true);
}

void CPlayer::AddPlayerCallback(LPUPDATEPLAYERCALLBACK pFunction, LPVOID pContext)
{
	CALLBACKFUNCTION *pUpdatePlayer = new CALLBACKFUNCTION[m_nUpdatePlayerCount+1];
	if (m_nUpdatePlayerCount > 0) memcpy(pUpdatePlayer, m_pUpdatePlayer, sizeof(CALLBACKFUNCTION)*m_nUpdatePlayerCount);
	if (m_pUpdatePlayer) delete [] m_pUpdatePlayer;
    pUpdatePlayer[m_nUpdatePlayerCount].m_pfCallback = (LPVOID)pFunction;
    pUpdatePlayer[m_nUpdatePlayerCount].m_pContext = pContext;
	m_pUpdatePlayer = pUpdatePlayer;
    m_nUpdatePlayerCount++;
}

void CPlayer::AddCameraCallback(LPUPDATECAMERACALLBACK pFunction, LPVOID pContext)
{
	CALLBACKFUNCTION *pUpdateCamera = new CALLBACKFUNCTION[m_nUpdateCameraCount+1];
	if (m_nUpdateCameraCount > 0) memcpy(pUpdateCamera, m_pUpdateCamera, sizeof(CALLBACKFUNCTION)*m_nUpdateCameraCount);
	if (m_pUpdateCamera) delete [] m_pUpdateCamera;
    pUpdateCamera[m_nUpdateCameraCount].m_pfCallback = (LPVOID)pFunction;
    pUpdateCamera[m_nUpdateCameraCount].m_pContext = pContext;
	m_pUpdateCamera = pUpdateCamera;
    m_nUpdateCameraCount++;
}

void CPlayer::RemovePlayerCallback(LPUPDATEPLAYERCALLBACK pFunction, LPVOID pContext)
{
    int i;

    for (i = 0; i < m_nUpdatePlayerCount; i++)
    {
        if ((m_pUpdatePlayer[i].m_pfCallback == pFunction) && (m_pUpdatePlayer[i].m_pContext == pContext)) break;
    } 
    if (i == m_nUpdatePlayerCount) return;
    if (i < (m_nUpdatePlayerCount-1))
    {
        memmove(&m_pUpdatePlayer[i], &m_pUpdatePlayer[i+1], (m_nUpdatePlayerCount-2) * sizeof(CALLBACKFUNCTION));    
    } 
    m_nUpdatePlayerCount--;
}

void CPlayer::RemoveCameraCallback(LPUPDATECAMERACALLBACK pFunction, LPVOID pContext)
{
    int i;

    for (i = 0; i < m_nUpdateCameraCount; i++)
    {
        if ((m_pUpdateCamera[i].m_pfCallback == pFunction) && (m_pUpdateCamera[i].m_pContext == pContext)) break;
    } 
    if (i == m_nUpdateCameraCount) return;
    if (i < (m_nUpdateCameraCount-1))
    {
        memmove(&m_pUpdateCamera[i], &m_pUpdateCamera[i+1], (m_nUpdateCameraCount-2) * sizeof(CALLBACKFUNCTION));
    } 
    m_nUpdateCameraCount--;
}

void CPlayer::Render(LPDIRECT3DDEVICE9 pDevice, CCamera* Camera, CScene* Scene)
{
    CGameObject *pObject = NULL;

    if (m_pCamera)
    {
        if (m_nCameraMode == THIRD_PERSON_CAMERA) pObject = m_p3rdPersonObject;  
    }
    else
    {
        pObject = m_p3rdPersonObject;    
    }    
    if (pObject && pDevice)
	{    
		D3DXMATRIX *pMatrix = &pObject->m_mtxWorld;
		pMatrix->_11 = m_vRight.x; pMatrix->_21 = m_vUp.x; pMatrix->_31 = m_vLookAt.x;
		pMatrix->_12 = m_vRight.y; pMatrix->_22 = m_vUp.y; pMatrix->_32 = m_vLookAt.y;
		pMatrix->_13 = m_vRight.z; pMatrix->_23 = m_vUp.z; pMatrix->_33 = m_vLookAt.z;
		pMatrix->_41 = m_vPosition.x;
		pMatrix->_42 = m_vPosition.y;
		pMatrix->_43 = m_vPosition.z;

		if(m_bBaseTransform) 
			*pMatrix = m_mMeshBaseTransform * (*pMatrix);

		if(Camera && Scene)
			pObject->Render(pDevice, Camera, Scene);
		else if(Camera) 
			pObject->Render(pDevice, Camera);
		else
			pObject->Render(pDevice);
			
	}
}


void CPlayer::SetBaseRotation(const D3DXMATRIX& RotationMatrix) {
	if(D3DXMatrixIsIdentity(&RotationMatrix)) {
		m_bBaseTransform = false;
		D3DXMatrixIdentity(&m_mMeshBaseTransform);
	}
	else {
		m_bBaseTransform = true;
		m_mMeshBaseTransform = RotationMatrix;
		
	}
}

void CPlayer::SetPitchForTerrain(CTerrain* Terrain) {
	D3DXVECTOR3 Up[5];
	Up[0] = Terrain->m_HeightMap.GetHeightMapNormal(this->m_vPosition.x, this->m_vPosition.z);
	Up[1] = Terrain->m_HeightMap.GetHeightMapNormal(this->m_vPosition.x + (this->m_bcBoundingCube.m_vMinimum.x), this->m_vPosition.z + (this->m_bcBoundingCube.m_vMinimum.z));
	Up[2] = Terrain->m_HeightMap.GetHeightMapNormal(this->m_vPosition.x + (this->m_bcBoundingCube.m_vMinimum.x), this->m_vPosition.z + (this->m_bcBoundingCube.m_vMaximum.z));
	Up[3] = Terrain->m_HeightMap.GetHeightMapNormal(this->m_vPosition.x + (this->m_bcBoundingCube.m_vMaximum.x), this->m_vPosition.z + (this->m_bcBoundingCube.m_vMinimum.z));
	Up[4] = Terrain->m_HeightMap.GetHeightMapNormal(this->m_vPosition.x + (this->m_bcBoundingCube.m_vMaximum.x), this->m_vPosition.z + (this->m_bcBoundingCube.m_vMaximum.z));
	
	Up[0] = Up[0] + Up[1] + Up[2] + Up[3] + Up[4];
	D3DXVec3Normalize(&Up[0], &Up[0]);

	//D3DXVECTOR3 Direction = Up[0] - m_vUp;
	//Direction /= 120;
	//Up[0] += Direction;
	//D3DXVec3Normalize(&Up[0], &Up[0]);
	m_vUp = Up[0];		

	D3DXVec3Cross(&m_vLookAt, &m_vRight, &m_vUp);
	D3DXVec3Cross(&m_vRight, &m_vUp, &m_vLookAt);
}

float CPlayer::GetFloat(void) {
	return m_fFloat;
}

void CPlayer::SetFloat(const float& PlayerFloat) {
	m_fFloat = PlayerFloat;
}

D3DXMATRIX CPlayer::GetTransform(void) {
	D3DXMATRIX Matrix;
	D3DXMatrixIdentity(&Matrix);
	
	Matrix._11 = m_vRight.x; Matrix._21 = m_vUp.x; Matrix._31 = m_vLookAt.x;
	Matrix._12 = m_vRight.y; Matrix._22 = m_vUp.y; Matrix._32 = m_vLookAt.y;
	Matrix._13 = m_vRight.z; Matrix._23 = m_vUp.z; Matrix._33 = m_vLookAt.z;
	Matrix._41 = m_vPosition.x;
	Matrix._42 = m_vPosition.y;
	Matrix._43 = m_vPosition.z;
	//Matrix._44 = 1.0f;

	return Matrix;	
}